#!/usr/bin/env python
# PyKismet: Python Akismet Module
# Copyright (C) 2006  Adam Blinkinsop <blinks@acm.org>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
APP_NAME = "PyKismet"
VERSION = "1.0"
AKISMET_HOST = "rest.akismet.com"
AKISMET_VERSION = "1.1"
from urllib2 import Request, urlopen
from urllib import urlencode

class AkismetError(Exception): pass
class InvalidAPIKey(AkismetError): pass
class UndefinedResult(AkismetError): pass

def call_akismet(function,
        api_key=None, # API key, included in host if given.
        host=AKISMET_HOST, version=AKISMET_VERSION,
        user_agent="%s/%s" % (APP_NAME, VERSION),
        **args):
    """
    Sends an HTTP request to Akismet that equates to a certain
    function call.  This method is really a utility function for
    the methods defined in the Akismet class itself.
    """
    if api_key: url = "http://%s.%s/%s/%s" % (api_key, host, version, function)
    else:       url = "http://%s/%s/%s" % (host, version, function)
    request = Request(url, urlencode(args), {'User-Agent': user_agent})
    response = urlopen(request)
    return response.read()

def verify_key(key, blog):
    """
    This method is called automatically by the Akismet class to verify the
    API provided on init.  It can also be used to validate a key without
    instantiating an Akismet object, at your convenience.
    """
    result = call_akismet('verify-key', key=key, blog=blog)
    if result == 'invalid':
        raise InvalidAPIKey("The API key '%s' is invalid." % key)
    elif result == 'valid':
        pass # key is ok
    else: # some strange result
        raise UndefinedResult("'%s' when validating an API key." % result)

class Akismet:
    def __init__(self, key, blog):
        """
        Store the API key and blog url (Must be a full URI, including http://.)
        and verify that the key is valid.  These two parameters are used in all
        the API functions.

        If the API key is invalid, the InvalidAPIKey exception will be thrown.
        """
        # validate the API key (throws exception on failure)
        verify_key(key, blog)
        # store required params for later use
        self.key = key
        self.blog = blog

    def comment_check(self, user_ip, user_agent, **args):
        """From the API documentation:
        This is basically the core of everything. This call takes a number of 
        arguments and characteristics about the submitted content and then 
        returns a thumbs up or thumbs down. Almost everything is optional, but 
        performance can drop dramatically if you exclude certain elements. I 
        would recommend erring on the side of too much data, as everything is 
        used as part of the Akismet signature.
        """
        if 'blog' not in args: args['blog'] = self.blog
        result = call_akismet('comment-check', self.key,
                user_ip=user_ip, user_agent=user_agent, **args)
        if result == 'true':    return True
        elif result == 'false': return False
        else: raise UndefinedResult("'%s' when checking a comment." % result)

    def submit_spam(self, user_ip, user_agent, **args):
        """From the API documentation:
        This call is for submitting comments that weren't marked as spam but 
        should have been. It takes identical arguments as comment_check.
        """
        if 'blog' not in args: args['blog'] = self.blog
        result = call_akismet('submit-spam', self.key,
                user_ip=user_ip, user_agent=user_agent, **args)
        
    def submit_ham(self, user_ip, user_agent, **args):
        """From the API documentation:
        This call is intended for the marking of false positives, things that 
        were incorrectly marked as spam. It takes identical arguments as 
        comment_check and submit_spam.
        """
        if 'blog' not in args: args['blog'] = self.blog
        result = call_akismet('submit-ham', self.key,
                user_ip=user_ip, user_agent=user_agent, **args)

class Comment:
    def __init__(self, user_ip, user_agent, referrer=None, permalink=None,
            type=None, author=None, email=None, url=None, content=None):
        """From the API documentation:
        user_ip (required)
            IP address of the comment submitter.
        user_agent (required)
            User agent information.
        referrer (note spelling)
            The content of the HTTP_REFERER header should be sent here.
        permalink
            The permanent location of the entry the comment was submitted to.
        comment_type
            May be blank, comment, trackback, pingback, or a made up value like 
            "registration".
        comment_author
            Submitted name with the comment
        comment_author_email
            Submitted email address
        comment_author_url
            Commenter URL.
        comment_content
            The content that was submitted.
        """
        self.params = {'user_ip': user_ip, 'user_agent': user_agent}
        if referrer:    self.params['referrer'] = referrer
        if permalink:   self.params['permalink'] = permalink
        if type:    self.params['comment_type'] = type
        if author:  self.params['comment_author'] = author
        if email:   self.params['comment_author_email'] = email
        if url:     self.params['comment_author_url'] = url
        if content: self.params['comment_content'] = content
    
    def is_spam(self, akismet, flag=None):
        """
        If flag is not set, this method returns a boolean that answers
        the question, "Is this comment spam?"
        If the flag is set, this method either...
          True:  tells Akismet that this comment is spam.
          False: tells Akismet that this comment is not spam.
        """
        if flag is None:
            return akismet.comment_check(**self.params)
        elif flag is True:
            akismet.submit_spam(**self.params)
        elif flag is False:
            akismet.submit_ham(**self.params)
        
    def is_ham(self, akismet, flag=None):
        """See is_spam."""
        if flag is None: return not self.is_spam(akismet)
        else: self.is_spam(akismet, not flag)
